package org.jenkinsci.plugins.p4scm;

import hudson.FilePath;
import hudson.Launcher;
import hudson.Proc;
import hudson.model.TaskListener;
import hudson.remoting.FastPipedInputStream;
import hudson.remoting.FastPipedOutputStream;
import hudson.remoting.RemoteInputStream;
import hudson.remoting.RemoteOutputStream;
import hudson.remoting.VirtualChannel;
import hudson.util.StreamTaskListener;
import hudson.Launcher.LocalLauncher;
import hudson.Proc.RemoteProc;
import hudson.remoting.Callable;
import hudson.remoting.Future;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;

import org.jenkinsci.remoting.RoleChecker;

import jenkins.model.Jenkins;

import com.tek42.perforce.PerforceException;

public class P4RemoteExecutor extends AbstractP4Executor {
 
    public P4RemoteExecutor(Launcher launcher, Map<String, String> envMap, FilePath filePath) {
        super(launcher, envMap, filePath);
    }

    @SuppressWarnings("deprecation")
    @Override
    public void exec(String[] cmd) throws PerforceException {
        try {
            // ensure we actually have a valid hudson launcher
            if (null == launcher) {
                launcher = Jenkins.getInstance().createLauncher(new StreamTaskListener(System.out));
            }
            
            VirtualChannel channel = launcher.getChannel();

            // hudsonOut->p4in->reader
            FastPipedOutputStream hudsonOut = new FastPipedOutputStream();
            FastPipedInputStream p4in = new FastPipedInputStream(hudsonOut);
            input = p4in;

            // hudsonIn<-p4Out<-writer
            FastPipedInputStream hudsonIn = new FastPipedInputStream();
            FastPipedOutputStream p4out = new FastPipedOutputStream(hudsonIn);
            output = p4out;

            final OutputStream out = hudsonOut == null ? null : new RemoteOutputStream(hudsonOut);
            final InputStream  in  = hudsonIn ==null ? null : new RemoteInputStream(hudsonIn);

            String remotePath = filePath.getRemote();
            TaskListener listener = launcher.getListener();
            RemoteCall remoteCall = new RemoteCall(
                    Arrays.asList(cmd), env, in, out, null,
                    remotePath,
                    listener);
            Future future = channel.callAsync(remoteCall);
            currentProcess = new RemoteProc(future);

        } catch(IOException e) {
            //try to close all the pipes before throwing an exception
            close();

            throw new PerforceException("Could not run perforce command.", e);
        }
    }
    
    private static class RemoteCall implements Callable<Integer,IOException> {
        private final List<String> cmd;
        private final String[] env;
        private final InputStream in;
        private final OutputStream out;
        private final OutputStream err;
        private final String workDir;
        private final TaskListener listener;

        RemoteCall(List<String> cmd, String[] env, InputStream in, OutputStream out, OutputStream err, String workDir, TaskListener listener) {
            this.cmd = new ArrayList<String>(cmd);
            this.env = env;
            this.in = in;
            this.out = out;
            this.err = err;
            this.workDir = workDir;
            this.listener = listener;
        }
        
        public Integer call() throws IOException {
            Launcher.ProcStarter ps = new LocalLauncher(listener).launch();
            ps.cmds(cmd).envs(env).stdin(in).stdout(out);
            if(err != null) ps.stderr(err);
            if(workDir!=null)   ps.pwd(workDir);

            Proc p;
            try {
                p = ps.start();
                Integer ret = p.join();
                if(out!=null) out.close();
                if(err!=null) err.close();
                return ret;
            } catch (InterruptedException e) {
                if(out!=null) out.close();
                if(err!=null) err.close();
                return -1;
            } catch (IOException e) {
                if(out!=null) out.close();
                if(err!=null) err.close();
                throw new IOException(e);
            }
        }
        
        @Override
        public void checkRoles(RoleChecker checker) throws SecurityException {
            
        }
    }
}
